URP/LWRP Shader实现描边效果 您所在的位置:网站首页 shader outline URP/LWRP Shader实现描边效果

URP/LWRP Shader实现描边效果

2024-07-11 09:25| 来源: 网络整理| 查看: 265

2021.1.11 更新: 我觉得我写得比较老了,可以看看下面新整理的文章 LWRP/URP/HDRP中的描边shader:https://zhuanlan.zhihu.com/p/354190065

索引 1. 给Unity内置的基础shader添加自定义属性2. 免费插件QuickOutline3. 整合:把QuickOutline的描边功能写进Unity内置的shader4. LWRP实现multi-pass

1. 给Unity内置的基础shader添加自定义属性

在这里插入图片描述 (不要被第一张图劝退,最终效果图在最后面>.< 很完美的~~) 我尝试的这种描边方法,边缘有断开的bug,解决方法①法线做插值???②据说用RenderTexture可以解决 ③用别人写的插件④使用Stencil测试 描边Pass可以参考这篇教程,非常详实 https://blog.csdn.net/puppet_master/article/details/54000951 在这里插入图片描述——我在URP内置的Lit.shader上添加了描边属性(描边颜色和宽度),并且重新定义了Priority区间(从±50扩展为±1500),显示材质球RenderQueue的大小(省去了你切换到Debug模式查看CustomRenderQueue的功夫) (对于shader我刚刚入门,写这个就想整理下这三天捣鼓的一点东西,可能有愚蠢错误的地方,还望指教)

第一个Pass实现描边效果,第二个Pass实现基础着色。第二个Pass不想自己写的话,也可以用UsePass来借用别的shader。 但是在URP/LWRP里默认只走一个Pass,如果想要运行多个Pass,可以使用Tags:

Tags{"LightMode" = "UniversalForward"} //LWRP不可用此Tag Tags{"LightMode" = "LightweightForward" } Tags{"LightMode" = "SRPDefaultUnlit"}

一个Pass里放一个Tags,这样看的话似乎URP-shader最多3个Pass,LWRP-shader最多2个Pass?

“To do a multi pass shader in the Lightweight pipeline you have to have one pass have no lightmode defined (or be using the default, which is “LightMode” = “SRPDefaultUnlit”), and one pass use “LightMode” = “LightweightForward”. The lightweight pipeline appears to only use the first pass it finds of each tag, so no more 3+ pass shaders.”——来自 https://forum.unity.com/threads/transparency-using-the-lwrp.550711/

在这里插入图片描述

先来看看URP内置的Lit.shader:↑上图

把它的Properties全部复制黏贴进我们的描边shader使用UsePass调用它的5个Pass(只用第一个Pass好像也够了)(?)它第一个Pass已经使用了Tags{“LightMode” = “UniversalForward”} ,所以如果我们想再加一个描边Pass,就必须在Pass里加上其他Tags,或者什么都不加也可以(什么都不加就是默认"LightMode" = “SRPDefaultUnlit” )(?)为了获得和原Lit.shader一致的GUI布局,注意最后一行【CustomEditor “UnityEditor . Rendering . Universal . ShaderGUI . LitOutlineShaderAddQueue”】,当然这也不是必须的,就是写一个shaderGUI脚本会让面板好看点罢了

新shader命名为LitOutline:↓

//这是一个在URP/Lit基础上加入了描边Pass的shader //如果看不到描边效果,是因为被后渲染的天空盒覆盖了,需要设置Render Queue=Transparent //请配合使用脚本 LitOutlineShaderAddQueue.cs 以获得和原shader一致的GUI布局 Shader "Universal Render Pipeline/LitOutline" { Properties { _OutlineCol("OutlineCol", Color) = (1,1,0,1) _OutlineFactor("OutlineFactor", Range(0, 1)) = 0.1 _ShowRenderQueue("ShowRenderQueue",Int)= -1 //... //下面照搬Lit.shader的Properties //复制粘贴大段代码页面会崩溃,我这里就……懒一下 } SubShader { Tags{"RenderType" = "Transparent" "RenderPipeline" = "UniversalPipeline" "IgnoreProjector" = "True"} LOD 300 UsePass "Universal Render Pipeline/Lit/ForwardLit" //我也不知道后面四个pass具体什么作用,这样引用是否能完整拷贝Lit-shader? //如果你用Frame Debug查看的话会发现下面4个Pass并没有用到,所以…大概可以删除? UsePass "Universal Render Pipeline/Lit/ShadowCaster" UsePass "Universal Render Pipeline/Lit/DepthOnly" UsePass "Universal Render Pipeline/Lit/Meta" UsePass "Universal Render Pipeline/Lit/Universal2D" Pass { Name "Outline" Cull Front Zwrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" struct v2f { UNITY_FOG_COORDS(0) float4 vertex : SV_POSITION; } fixed4 _OutlineCol; float _OutlineFactor; v2f vert (appdata_base v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);//将法线方向转换到视空间 vnormal = normalize(vnormal);//为了_OutlineFactor不受物体scale影响 float2 offset = TransformViewToProjection(vnormal.xy);//将视空间法线xy坐标转化到投影空间 o.vertex.xy += offset * _OutlineFactor;//在最终投影阶段输出进行偏移操作 UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { return _OutlineCol; } ENDCG } } FallBack "Hidden/Universal Render Pipeline/FallbackError" CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.LitOutlineShaderAddQueue" }

现在,我们写完了LitOutline.shader后还要再仿照原来的LitShader.cs写一个shaderGUI脚本 ——直接复制一份LitShader.cs然后重命名为LitOutlineShaderAddQueue.cs开始修改:

想要实现的功能1:在面板里显示描边参数 添加MaterialProperty类型的变量,outlineCol, outlineFactor,showRenderQueue 然后用FindProperty()函数将变量与shader中的属性对应, 最后在OnGUI()函数里用 materialEditor.ShaderProperty()将变量显示到面板上想要实现的功能2:将Priority的区间从±50扩展为±1500 为了修改priority的区间,在BaseShaderGUI脚本里查找了一下它是在DrawAdvancedOptions()函数里诞生的,所以重写了这个函数想要实现的功能3:在面板里显示当前的RenderQueue 为了能时刻显示当前的RenderQueue,在BaseShaderGUI脚本里发现它是由MaterialChanged()函数更新的,所以也修改了下这个函数

这个GUI脚本命名为LitOutlineShaderAddQueue.cs:↓ 这里就粘贴了我修改和添加的代码行,其他照搬的就点点点表示了

using System; using UnityEngine; using UnityEngine.Rendering; using UnityEditor.Rendering.Universal; //这个脚本一定要保存在Editor文件夹下,负责提供 LitOutline.shader的GUI布局 //这个脚本以LitShader.cs为基础,将priority区间扩大至±1000,并且显示RenderQueue数值(RenderQueue不可编辑) //添加了变量【outlineCol, outlineFactor】 变量【showRenderQueue】 //修改了FindProperties()函数 //修改了MaterialChanged()函数 //修改了DrawAdvancedOptions()函数(使queueOffsetRange = 1000) //添加了public override void OnGUI()函数 namespace UnityEditor.Rendering.Universal.ShaderGUI { internal class LitOutlineShaderAddQueue : BaseShaderGUI { // Properties private LitGUI.LitProperties litProperties; //添加了变量【outlineCol, outlineFactor】 变量【showRenderQueue】 private MaterialProperty outlineCol, outlineFactor; protected MaterialProperty showRenderQueue { get; set; } // collect properties from the material properties public override void FindProperties(MaterialProperty[] properties) { base.FindProperties(properties); litProperties = new LitGUI.LitProperties(properties); //修改了FindProperties()函数 showRenderQueue = FindProperty("_ShowRenderQueue", properties, false); outlineCol = FindProperty("_OutlineCol", properties); outlineFactor = FindProperty("_OutlineFactor", properties); } // material changed check public override void MaterialChanged(Material material) { if (material == null) throw new ArgumentNullException("material"); SetMaterialKeywords(material, LitGUI.SetMaterialKeywords); //MaterialChanged()函数触发SetMaterialKeywords()函数触发SetupMaterialBlendMode(),从而更新了 material.renderQueue //修改了MaterialChanged()函数 showRenderQueue.floatValue = material.renderQueue; } public override void DrawSurfaceOptions(Material material){...} public override void DrawSurfaceInputs(Material material){...} public override void DrawAdvancedOptions(Material material) { if (litProperties.reflections != null && litProperties.highlights != null) { EditorGUI.BeginChangeCheck(); materialEditor.ShaderProperty(litProperties.highlights, LitGUI.Styles.highlightsText); materialEditor.ShaderProperty(litProperties.reflections, LitGUI.Styles.reflectionsText); if (EditorGUI.EndChangeCheck()) { MaterialChanged(material); } } //修改了DrawAdvancedOptions()函数(使queueOffsetRange = 1000) //base.DrawAdvancedOptions(material); materialEditor.EnableInstancingField(); if (queueOffsetProp != null) { EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = queueOffsetProp.hasMixedValue; int queueOffsetRange = 1500; //和原先相比就添加了这一行 var queue = EditorGUILayout.IntSlider(Styles.queueSlider, (int)queueOffsetProp.floatValue, -queueOffsetRange, queueOffsetRange); if (EditorGUI.EndChangeCheck()) { queueOffsetProp.floatValue = queue; } EditorGUI.showMixedValue = false; } } public override void AssignNewShaderToMaterial(...){...} //添加了public override void OnGUI()函数 public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props) { // render the default gui base.OnGUI(materialEditor, props); materialEditor.ShaderProperty(showRenderQueue, new GUIContent("RenderQueue(uneditable,controlled by Priority)")); materialEditor.ShaderProperty(outlineCol, new GUIContent("OutlineColor")); materialEditor.ShaderProperty(outlineFactor, new GUIContent("OutlineFactor")); } } }

LWRP与URP的shader/shaderGUI脚本都是通用的,只需要把出现Lightweight Render Pipeline的地方替换Universal Render Pipeline即可。(还有FallBack里的备用shader也要修改)

2. 免费插件QuickOutline

在这里插入图片描述 打开AssetStore,下载QuickOutline 给你的物体添加Outline.cs脚本,设置描边模式、颜色和粗细,点击运行即可。 原理:脚本会为你的物体添加2个新的材质球,一个是Mask(使用Stencil将物体本体区域标记为1),一个是Fill(将物体本体扩展填充,然后根据Stencil测试把标记为1的区域剔除),注意渲染队列是Fill在Mask后面渲染。

如果你使用Stencil测试,Pass的渲染顺序就是很重要的——关于渲染顺序的问题,请多多用Frame Debug窗口查看~

优点:不会出现明显的断裂;凹面处不会描边;描边功能丰富;描边不会近粗远细 缺点:无法使用于多材质球(有多个sub-mesh)的物体(你会发现只有最后一个sub-mesh得到了描边) 原因:

如果物体只有一个single-mesh,在unity里为它添加多个材质球,相当于添加了不同的shader-Pass(你还可以通过控制材质球的渲染队列RenderQueue来控制Pass的渲染顺序)。 但是如果物体有多个submesh(在建模软件里物体已经被赋予多种材质),在unity里会显示材质列表,这个列表是submesh和材质球之间的对应关系,所以你不能通过在列表里追加一个材质球就让它应用给所有的submesh。事实上,你追加的材质球只会被应用给最后一个submesh。 来自https://forum.unity.com/threads/blending-with-all-materials-of-submeshes.51677/ Noisecrime给出了4种方案: A. 丢弃原来的材质球,赋予新的材质球 B. 重新写shader,把新的属性添加进原来的shader里 D. 复制一份物体,把他的材质球全部替换为新的材质球,这相当于原来的物体负责原来的着色,新复制的物体负责新的材质效果

D→解决方法1: 原物体保留原有的材质球,复制一份物体全部替换为Fill材质球,再复制一份物体全部替换为Mask材质球~完结撒花 在这里插入图片描述 B→解决方法2: 为Mask的Pass添加标签Tags{“LightMode” = “LightweightForward” },Fill的描边Pass可以不添加标签,然后把这两个Pass整合进你的shader,你可以复制粘贴代码,也可以用UsePass这样的语句。

具体URP的代码看下面↓

3. 整合:把QuickOutline的描边功能写进Unity内置的shader 在Properties里添加了属性_ZTest4Mask,_ZTest4Fill第1个Pass,走URP/Lit,主着色第2个Pass,Mask,使用Stencil测试将要剔除的区域标记为1第3个Pass,Fill,使用Stencil测试保留剔除区域外的填充,即描边最后更新了GUI脚本【CustomEditor “UnityEditor.Rendering.Universal.ShaderGUI.LitOutlineShaderAddQueueZTest”】

shader完整代码:↓

//这是一个在URP/Lit基础上使用Stencil测试做遮罩实现描边效果的shader //第1个Pass,走URP/Lit,主着色 //第2个Pass,Mask,使用Stencil测试将要剔除的区域标记为1 //第3个Pass,Fill,使用Stencil测试保留剔除区域外的填充,即描边 //请配合使用脚本 LitOutlineShaderAddQueueZTest.cs 以获得和原shader一致的GUI布局 Shader "Universal Render Pipeline/LitOutline3" { Properties { [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest4Mask("ZTest_mask", Float) = 8 //8表示Always [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest4Fill("ZTest_fill", Float) = 8 _OutlineCol("OutlineCol", Color) = (1,1,0,1) _OutlineFactor("OutlineFactor", Range(0, 10)) = 2 _ShowRenderQueue("ShowRenderQueue",Int)= -1 //使用脚本 LitOutlineShaderAddQueue.cs:Priority参数±1500,RenderQueue参数只读 // Specular vs Metallic workflow [HideInInspector] _WorkflowMode("WorkflowMode", Float) = 1.0 [MainColor] _BaseColor("Color", Color) = (1,1,1,1) [MainTexture] _BaseMap("Albedo", 2D) = "white" {} _Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5 _Smoothness("Smoothness", Range(0.0, 1.0)) = 0.5 _GlossMapScale("Smoothness Scale", Range(0.0, 1.0)) = 1.0 _SmoothnessTextureChannel("Smoothness texture channel", Float) = 0 [Gamma] _Metallic("Metallic", Range(0.0, 1.0)) = 0.0 _MetallicGlossMap("Metallic", 2D) = "white" {} _SpecColor("Specular", Color) = (0.2, 0.2, 0.2) _SpecGlossMap("Specular", 2D) = "white" {} [ToggleOff] _SpecularHighlights("Specular Highlights", Float) = 1.0 [ToggleOff] _EnvironmentReflections("Environment Reflections", Float) = 1.0 _BumpScale("Scale", Float) = 1.0 _BumpMap("Normal Map", 2D) = "bump" {} _OcclusionStrength("Strength", Range(0.0, 1.0)) = 1.0 _OcclusionMap("Occlusion", 2D) = "white" {} _EmissionColor("Color", Color) = (0,0,0) _EmissionMap("Emission", 2D) = "white" {} // Blending state [HideInInspector] _Surface("__surface", Float) = 0.0 [HideInInspector] _Blend("__blend", Float) = 0.0 [HideInInspector] _AlphaClip("__clip", Float) = 0.0 [HideInInspector] _SrcBlend("__src", Float) = 1.0 [HideInInspector] _DstBlend("__dst", Float) = 0.0 [HideInInspector] _ZWrite("__zw", Float) = 1.0 [HideInInspector] _Cull("__cull", Float) = 2.0 _ReceiveShadows("Receive Shadows", Float) = 1.0 // Editmode props [HideInInspector] _QueueOffset("Queue offset", Float) = 0.0 // ObsoleteProperties [HideInInspector] _MainTex("BaseMap", 2D) = "white" {} [HideInInspector] _Color("Base Color", Color) = (1, 1, 1, 1) [HideInInspector] _GlossMapScale("Smoothness", Float) = 0.0 [HideInInspector] _Glossiness("Smoothness", Float) = 0.0 [HideInInspector] _GlossyReflections("EnvironmentReflections", Float) = 0.0 } SubShader { // Universal Pipeline tag is required. If Universal render pipeline is not set in the graphics settings // this Subshader will fail. One can add a subshader below or fallback to Standard built-in to make this // material work with both Universal Render Pipeline and Builtin Unity Pipeline Tags{"RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "IgnoreProjector" = "True"} LOD 300 // ------------------------------------------------------------------ // Forward pass. Shades all light in a single pass. GI + emission + Fog UsePass "Universal Render Pipeline/Lit/ForwardLit" //UsePass "Custom/Outline Mask/Mask" //如果使用UsePass记得修改shader添加Tags Pass { //copy from the unity addon QuickOutline Name "Mask" Tags{ "LightMode" = "LightweightForward" } //This line is important! Cull Off ZTest [_ZTest4Mask] ZWrite Off ColorMask 0 Stencil { Ref 1 Pass Replace } } //UsePass "Custom/Outline Fill/Fill" Pass { //copy from the unity addon QuickOutline Name "Fill" Cull Off ZTest [_ZTest4Fill] ZWrite Off Blend SrcAlpha OneMinusSrcAlpha ColorMask RGB Stencil { Ref 1 Comp NotEqual } CGPROGRAM #include "UnityCG.cginc" #pragma vertex vert #pragma fragment frag struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float3 smoothNormal : TEXCOORD3; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 position : SV_POSITION; fixed4 color : COLOR; UNITY_VERTEX_OUTPUT_STEREO }; uniform fixed4 _OutlineCol; uniform float _OutlineFactor; v2f vert(appdata input) { v2f output; UNITY_SETUP_INSTANCE_ID(input); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); float3 normal = any(input.smoothNormal) ? input.smoothNormal : input.normal; float3 viewPosition = UnityObjectToViewPos(input.vertex); float3 viewNormal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, normal)); output.position = UnityViewToClipPos(viewPosition + viewNormal * -viewPosition.z * _OutlineFactor / 1000.0); output.color = _OutlineCol; return output; } fixed4 frag(v2f input) : SV_Target { return input.color; } ENDCG } } FallBack "Hidden/Universal Render Pipeline/FallbackError" //请配合使用脚本 LitOutlineShaderAddQueueZTest.cs 以获得和原shader一致的GUI布局 CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.LitOutlineShaderAddQueueZTest"// Priority参数±1500,RenderQueue参数只读,增加了ZTest参数 }

因为新加了2个ZTest属性,所以shaderGUI脚本也要小小改动一下:

添加MaterialProperty类型的变量,ztest_mask, ztest_fill然后用FindProperty()函数将变量与shader中的属性对应最后在OnGUI()函数里用 materialEditor.ShaderProperty()将变量显示到面板上

shaderGUI完整代码:↓

using System; using UnityEngine; using UnityEngine.Rendering; using UnityEditor.Rendering.Universal; //这个脚本一定要保存在Editor文件夹下,负责提供LitOutline3.shader的GUI布局 //这个脚本以LitShader.cs为基础 //LitOutlineShaderAddQueueZTest.cs 将priority区间扩大至±1500,并且显示RenderQueue数值(RenderQueue不可编辑) //添加了【outlineCol, outlineFactor】 //添加了【showRenderQueue】 //添加了【ztest_mask, ztest_fill】 //修改了FindProperties()函数 //修改了MaterialChanged()函数 //修改了DrawAdvancedOptions()函数(使queueOffsetRange = 1000) //添加了【public override void OnGUI()】 namespace UnityEditor.Rendering.Universal.ShaderGUI { internal class LitOutlineShaderAddQueueZTest : BaseShaderGUI { // Properties private LitGUI.LitProperties litProperties; private MaterialProperty outlineCol, outlineFactor; protected MaterialProperty showRenderQueue { get; set; } private MaterialProperty ztest_mask, ztest_fill; #region // LitShader.cs // collect properties from the material properties public override void FindProperties(MaterialProperty[] properties) { base.FindProperties(properties); litProperties = new LitGUI.LitProperties(properties); showRenderQueue = FindProperty("_ShowRenderQueue", properties, false); outlineCol = FindProperty("_OutlineCol", properties); outlineFactor = FindProperty("_OutlineFactor", properties); ztest_mask = FindProperty("_ZTest4Mask", properties); ztest_fill = FindProperty("_ZTest4Fill", properties); } // material changed check public override void MaterialChanged(Material material) { if (material == null) throw new ArgumentNullException("material"); SetMaterialKeywords(material, LitGUI.SetMaterialKeywords); //MaterialChanged()函数触发SetMaterialKeywords()函数触发SetupMaterialBlendMode(),从而更新了 material.renderQueue //material.renderQueue = renderqueue; //material.renderQueue = (int)renderqueueProp.floatValue; showRenderQueue.floatValue = material.renderQueue; } // material main surface options public override void DrawSurfaceOptions(Material material) { if (material == null) throw new ArgumentNullException("material"); // Use default labelWidth EditorGUIUtility.labelWidth = 0f; // Detect any changes to the material EditorGUI.BeginChangeCheck(); if (litProperties.workflowMode != null) { DoPopup(LitGUI.Styles.workflowModeText, litProperties.workflowMode, Enum.GetNames(typeof(LitGUI.WorkflowMode))); } if (EditorGUI.EndChangeCheck()) { foreach (var obj in blendModeProp.targets) MaterialChanged((Material)obj); } base.DrawSurfaceOptions(material); } // material main surface inputs public override void DrawSurfaceInputs(Material material) { base.DrawSurfaceInputs(material); LitGUI.Inputs(litProperties, materialEditor, material); DrawEmissionProperties(material, true); DrawTileOffset(materialEditor, baseMapProp); } // material main advanced options public override void DrawAdvancedOptions(Material material) { if (litProperties.reflections != null && litProperties.highlights != null) { EditorGUI.BeginChangeCheck(); materialEditor.ShaderProperty(litProperties.highlights, LitGUI.Styles.highlightsText); materialEditor.ShaderProperty(litProperties.reflections, LitGUI.Styles.reflectionsText); if (EditorGUI.EndChangeCheck()) { MaterialChanged(material); } } //base.DrawAdvancedOptions(material); materialEditor.EnableInstancingField(); if (queueOffsetProp != null) { EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = queueOffsetProp.hasMixedValue; int queueOffsetRange = 1500; //和原先相比就添加了这一行 var queue = EditorGUILayout.IntSlider(Styles.queueSlider, (int)queueOffsetProp.floatValue, -queueOffsetRange, queueOffsetRange); if (EditorGUI.EndChangeCheck()) { queueOffsetProp.floatValue = queue; } EditorGUI.showMixedValue = false; } } public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader) { if (material == null) throw new ArgumentNullException("material"); // _Emission property is lost after assigning Standard shader to the material // thus transfer it before assigning the new shader if (material.HasProperty("_Emission")) { material.SetColor("_EmissionColor", material.GetColor("_Emission")); } base.AssignNewShaderToMaterial(material, oldShader, newShader); if (oldShader == null || !oldShader.name.Contains("Legacy Shaders/")) { SetupMaterialBlendMode(material); return; } SurfaceType surfaceType = SurfaceType.Opaque; BlendMode blendMode = BlendMode.Alpha; if (oldShader.name.Contains("/Transparent/Cutout/")) { surfaceType = SurfaceType.Opaque; material.SetFloat("_AlphaClip", 1); } else if (oldShader.name.Contains("/Transparent/")) { // NOTE: legacy shaders did not provide physically based transparency // therefore Fade mode surfaceType = SurfaceType.Transparent; blendMode = BlendMode.Alpha; } material.SetFloat("_Surface", (float)surfaceType); material.SetFloat("_Blend", (float)blendMode); if (oldShader.name.Equals("Standard (Specular setup)")) { material.SetFloat("_WorkflowMode", (float)LitGUI.WorkflowMode.Specular); Texture texture = material.GetTexture("_SpecGlossMap"); if (texture != null) material.SetTexture("_MetallicSpecGlossMap", texture); } else { material.SetFloat("_WorkflowMode", (float)LitGUI.WorkflowMode.Metallic); Texture texture = material.GetTexture("_MetallicGlossMap"); if (texture != null) material.SetTexture("_MetallicSpecGlossMap", texture); } MaterialChanged(material); } #endregion public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props) { // render the default gui base.OnGUI(materialEditor, props); materialEditor.ShaderProperty(showRenderQueue, new GUIContent("RenderQueue(uneditable,controlled by Priority)")); materialEditor.ShaderProperty(outlineCol, new GUIContent("OutlineColor")); materialEditor.ShaderProperty(outlineFactor, new GUIContent("OutlineFactor")); materialEditor.ShaderProperty(ztest_mask, new GUIContent("ZTest of Pass-Mask")); materialEditor.ShaderProperty(ztest_fill, new GUIContent("ZTest of Pass-Fill")); } } }

补充:(补充也很重要) QuickOutline插件提供的Outline.cs脚本还具有【光滑法线】【Precompute】等功能 所以我们还需要参考重写这个脚本。

在这里插入图片描述

最后附一个鼠标悬停触发物体高亮的脚本:

//【光滑法线】【预先计算】的内容来自于插件QuickOutline的Outline.cs脚本 //20210111更新,追加 private int[] renderQueues,透明物体也可以享用这个描边shader啦 [DisallowMultipleComponent] public class HoverHighlight0618 : MonoBehaviour { #region ...鼠标悬停触发高亮描边 [SerializeField] private Color outlineColor = Color.white; [SerializeField, Range(0f, 10f)] private float outlineFactor = 2f; private float outlineFactor_temp = 2f; public Shader shader_highlighted; public int my_showRenderQueue = 3100;//要在渲染天空盒之后 private Shader[] originalShaders; private Renderer[] renderers; private int[] renderQueues; void Awake() { shader_highlighted = Shader.Find("Universal Render Pipeline/LitOutline3"); outlineMode = Mode.OutlineVisible; // Cache renderers renderers = GetComponentsInChildren(); //为了定义数组originalShaders,统计物体及其子物体共有多少个材质球 int count = 0; foreach(var renderer in renderers) { count += renderer.materials.Length; } originalShaders = new Shader[count];//存储每个材质球原来的shader renderQueues = new int[count]; int i = 0; foreach (var renderer in renderers) { foreach (var mat in renderer.materials) { originalShaders[i] = mat.shader; //存储每个材质球原来的shader renderQueues[i] = mat.renderQueue; i++; } } // Retrieve or generate smooth normals LoadSmoothNormals(); } //物体必须有collider 下面的语句才有效 private void OnMouseEnter()//替换shader;更改Priority来改变mat的render queue { foreach (var renderer in renderers) { foreach (var mat in renderer.materials) { outlineFactor_temp = outlineFactor; OutlineMode(); mat.shader = shader_highlighted; mat.SetColor("_OutlineCol", outlineColor); mat.SetFloat("_OutlineFactor", outlineFactor_temp); int myPriority = mat.renderQueue - my_showRenderQueue + 50;//BaseShaderGUI.queueOffsetRange=50 mat.SetInt("_QueueOffset", myPriority);//改变priority参数 mat.renderQueue = my_showRenderQueue;//加写这行是因为,如果inspector面板的材质球不展开,LitOutlineShaderAddQueueZTest.cs这个GUI脚本里的内容就不会执行,也就没有代码会执行renderQueue的更改 mat.SetFloat("_ZTest4Mask", ztest_mask); mat.SetFloat("_ZTest4Fill", ztest_fill); } } } private void OnMouseExit() { int i = 0; foreach (var renderer in renderers) { foreach (var mat in renderer.materials) { mat.shader = originalShaders[i]; mat.renderQueue = renderQueues[i]; mat.SetInt("_QueueOffset", 0);//改变priority参数 ??? i++; } } } #endregion #region ...5种描边模式 public enum Mode { OutlineAll, OutlineVisible, OutlineHidden, OutlineAndSilhouette, SilhouetteOnly } [SerializeField] private Mode outlineMode; private float ztest_mask, ztest_fill; void OutlineMode() { switch (outlineMode) { case Mode.OutlineAll: ztest_mask = (float)UnityEngine.Rendering.CompareFunction.Always; ztest_fill = (float)UnityEngine.Rendering.CompareFunction.Always; break; case Mode.OutlineVisible: ztest_mask= (float)UnityEngine.Rendering.CompareFunction.Always; ztest_fill= (float)UnityEngine.Rendering.CompareFunction.LessEqual; break; case Mode.OutlineHidden: ztest_mask = (float)UnityEngine.Rendering.CompareFunction.Always; ztest_fill = (float)UnityEngine.Rendering.CompareFunction.Greater; break; case Mode.OutlineAndSilhouette: ztest_mask = (float)UnityEngine.Rendering.CompareFunction.LessEqual; ztest_fill = (float)UnityEngine.Rendering.CompareFunction.Always; break; case Mode.SilhouetteOnly: ztest_mask = (float)UnityEngine.Rendering.CompareFunction.LessEqual; ztest_fill = (float)UnityEngine.Rendering.CompareFunction.Greater; outlineFactor_temp = 0; break; } } #endregion #region ...光滑法线 private static HashSet registeredMeshes = new HashSet(); [Serializable] private class ListVector3 { public List data; } [SerializeField, HideInInspector] private List bakeKeys = new List(); [SerializeField, HideInInspector] private List bakeValues = new List(); void LoadSmoothNormals() { // Retrieve or generate smooth normals foreach (var meshFilter in GetComponentsInChildren()) { // Skip if smooth normals have already been adopted if (!registeredMeshes.Add(meshFilter.sharedMesh)) { continue; } // Retrieve or generate smooth normals var index = bakeKeys.IndexOf(meshFilter.sharedMesh); var smoothNormals = (index >= 0) ? bakeValues[index].data : SmoothNormals(meshFilter.sharedMesh); // Store smooth normals in UV3 meshFilter.sharedMesh.SetUVs(3, smoothNormals); } // Clear UV3 on skinned mesh renderers foreach (var skinnedMeshRenderer in GetComponentsInChildren()) { if (registeredMeshes.Add(skinnedMeshRenderer.sharedMesh)) { skinnedMeshRenderer.sharedMesh.uv4 = new Vector2[skinnedMeshRenderer.sharedMesh.vertexCount]; } } Debug.Log("LoadSmoothNormals()"); } List SmoothNormals(Mesh mesh) { // Group vertices by location var groups = mesh.vertices.Select((vertex, index) => new KeyValuePair(vertex, index)).GroupBy(pair => pair.Key); // Copy normals to a new list var smoothNormals = new List(mesh.normals); // Average normals for grouped vertices foreach (var group in groups) { // Skip single vertices if (group.Count() == 1) { continue; } // Calculate the average normal var smoothNormal = Vector3.zero; foreach (var pair in group) { smoothNormal += mesh.normals[pair.Value]; } smoothNormal.Normalize(); // Assign smooth normal to each vertex foreach (var pair in group) { smoothNormals[pair.Value] = smoothNormal; } } Debug.Log("SmoothNormals"); return smoothNormals; } #endregion /// /// 如果勾选【PreCompute】 /// #region ...预先计算 [Header("Optional")] [SerializeField, Tooltip("Precompute enabled: Per-vertex calculations are performed in the editor and serialized with the object. " + "Precompute disabled: Per-vertex calculations are performed at runtime in Awake(). This may cause a pause for large meshes.")] private bool precomputeOutline; void OnValidate() { // Clear cache when baking is disabled or corrupted if (!precomputeOutline && bakeKeys.Count != 0 || bakeKeys.Count != bakeValues.Count) { bakeKeys.Clear(); bakeValues.Clear(); } // Generate smooth normals when baking is enabled if (precomputeOutline && bakeKeys.Count == 0) { Bake(); } } void Bake() { // Generate smooth normals for each mesh var bakedMeshes = new HashSet(); foreach (var meshFilter in GetComponentsInChildren()) { // Skip duplicates if (!bakedMeshes.Add(meshFilter.sharedMesh)) { continue; } // Serialize smooth normals var smoothNormals = SmoothNormals(meshFilter.sharedMesh); bakeKeys.Add(meshFilter.sharedMesh); bakeValues.Add(new ListVector3() { data = smoothNormals }); } Debug.Log("Bake()"); } #endregion } 4. LWRP实现multi-pass

上面的shader有3个pass,在URP里分配3个tags刚刚好够用,但是LWRP最多只允许2个Pass。 ——我想到一种解决方法是:把Mask的stencil测试添加进负责主着色的Pass,用Fill的描边Pass替换先前自己写的描边Pass,这样2个Pass就可以搞定了。尝试结果:失败。因为我不会移植主着色…我写不来代码替换这句【UsePass “Lightweight Render Pipeline/Lit/ForwardLit”】 ——第二种方法是添加ShaderTagId(参考:LWRP(URP)学习笔记二多PASS的使用)我使用的Unity版本是2019.1.3f1,在这里DrawObjectsPass这个脚本似乎叫做RenderOpaqueForwardPass和RenderTransparentForwardPass,把这两个脚本都添加新的shaderTag【“ForFill”】,然后shader的Fill-Pass添加【Tags{ “LightMode” = “ForFill” }】,

//RenderOpaqueForwardPass和RenderTransparentForwardPass.cs添加 m_ShaderTagIdList.Add(new ShaderTagId("ForFill")); //LitOutline.shader的Fill-Pass添加 Tags{ "LightMode" = "ForFill" }

这样shader就可以运行多Pass了,但是!!!关闭项目再打开,源码脚本又被覆盖回去了,我猜添加新的shaderTag的思路是对的,就是直接改源码这种操作不允许吧。 ——最后一种方法:好好学渲染管线吧…… https://gist.github.com/Elringus/69f0da9c71306f1ea0f575cc7568b31a

using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.LWRP; // Inheriting from `ScriptableRendererFeature` will add it to the // `Renderer Features` list of the custom LWRP renderer data asset. public class RenderMyCustomPass : ScriptableRendererFeature { private class MyCustomPass : ScriptableRenderPass { // Just a tag used to pick up a buffer from the pool. private const string commandBufferName = nameof(MyCustomPass); // Corresponds to `Tags { "LightMode" = "MyCustomPass" }` in the shaders. // You have to add this tag for the corresponding shaders to associate them with this pass. private static readonly ShaderTagId shaderTag = new ShaderTagId(nameof(MyCustomPass)); // An arbitrary name to store temporary render texture. private static readonly int tempRTPropertyId = Shader.PropertyToID("_TempRT"); // Name of the grab texture used in the shaders. private static readonly int grabTexturePropertyId = Shader.PropertyToID("_MyGrabTexture"); public MyCustomPass () { renderPassEvent = RenderPassEvent.AfterRenderingTransparents; } public override void Execute (ScriptableRenderContext context, ref RenderingData renderingData) { // Grab screen texture and assign it to a global texture property. var cmd = CommandBufferPool.Get(commandBufferName); cmd.GetTemporaryRT(tempRTPropertyId, renderingData.cameraData.cameraTargetDescriptor); cmd.Blit(BuiltinRenderTextureType.RenderTexture, tempRTPropertyId); cmd.SetGlobalTexture(grabTexturePropertyId, tempRTPropertyId); context.ExecuteCommandBuffer(cmd); cmd.Clear(); CommandBufferPool.Release(cmd); // Draw the objects that are using materials associated with this pass. var drawingSettings = CreateDrawingSettings(shaderTag, ref renderingData, SortingCriteria.CommonTransparent); var filteringSettings = new FilteringSettings(RenderQueueRange.transparent); context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings); } public override void FrameCleanup (CommandBuffer cmd) { base.FrameCleanup(cmd); cmd.ReleaseTemporaryRT(tempRTPropertyId); } } private MyCustomPass grabScreenPass; public override void Create () { grabScreenPass = new MyCustomPass(); } public override void AddRenderPasses (ScriptableRenderer renderer, ref RenderingData renderingData) { renderer.EnqueuePass(grabScreenPass); } }

在这里插入图片描述

搬运大佬的脚本,配置LWRP Asset,然后在shader的Fill-pass里加上

Tags { "LightMode" = "MyCustomPass" }

我终于在LWRP里实现了multi-pass……虽然看渲染管线的脚本还是犹如天书…… 在这里插入图片描述 更新: 你以为这样就能在LWRP里实现描边效果了吗?!?还没完…… 坑爹的LWRP,物体RenderQueue一旦大于2500,模型就Transparent了,它就会把它后方的阴影都展示出来 在这里插入图片描述 这样的话,我就必须限制物体RenderQueue小于等于2500,排在Opaque队列里。尝试了好久,最后回到了前面照抄搬运的脚本,配合着FrameDebugger窗口,大概看出个意思,新添加的Tags { “LightMode” = “MyCustomPass” }是定义在Transparent队列之后的,那么我现在就把所有出现“transparent”的语句都替换成opaque,于是哈带有Tags { “LightMode” = “MyCustomPass” }的Pass会运行在Opaque队列之后,天空盒之前。最后注意Fill-Pass的ZWrite要改为On 在这里插入图片描述 ————————————————————————————————————————

还没来得及看的东西:

https://answers.unity.com/questions/1660050/stencil-shader-no-longer-working-with-lightweight.html?childToView=1660770#answer-1660770https://www.techort.com/creating-an-outline-for-lwrp-in-unity-habrahabr/https://blog.csdn.net/nxl76450106/article/details/101290283

待学习:

CommandBuffer.DrawRenderer https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.DrawRenderer.html?_ga=2.156913403.138523371.1592461566-1907809638.1591859566 这个应该学的~~~

https://www.ronja-tutorials.com/2018/08/18/stencil-buffers.html

Multiple Materials per Object- is it really “bad”? https://forum.unity.com/threads/multiple-materials-per-object-is-it-really-bad.44712/

https://unity.cn/projects/unity-hdrp-custom-pass-post-processing-hou-chu-li-te-xiao-xue-xi-yi-zong-jie Unity HDRP Custom Pass (Post processing) 后处理特效学习(一)总结,文字版, 主要介绍如何获取各种延迟渲染的缓冲数据。 比起之前发的文字版,进行了更新,补充了很多内容和图,如果对这个感兴趣的务必重新看一下。

https://unity.cn/projects/unity-hdrp-custom-pass-post-processing-hou-chu-li-te-xiao-xue-xi-er-zong-jie Unity HDRP Custom Pass (Post processing) 后处理特效学习(二)总结,文字版, 主要介绍如何使用ddx、ddy进行勾边,,如何手动计算进行勾边。 比起视频,补充了很多内容和图。比如说补充了为什么ddx、ddy计算出来的勾边会断线,是GPU是如何计算的。

URP也可以做后处理,用RendererFeature,但是URP的后处理,你就没有GBuffer的数据了,那你只能从别的方面着手。就像官方的RendererFeature例子里的就有一个SobelFilter的勾边处理。 https://github.com/Unity-Technologies/UniversalRenderingExamples



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有